extern crate dirs;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::path::Path;
// use std::default::Default;
use colored::Colorize;
use log::info;
use std::fs;
use std::process::Command;
use std::str;

use super::config::*;
use super::routes::Parameter;

// const py_plugins : &str = "";
// const GIT_PLUGINS: &str = "--depth 1 https://github.com/sqlmapproject/sqlmap.git [SEP]";
// const ROOT_DIR:&str = ".mu-cache";
// const home_dir: String = dirs::home_dir().expect("no home dir").as_path().join(ROOT_DIR).to_str().unwrap().to_string();

#[derive(Serialize, Deserialize)]
pub struct SystemInfoAndBuild {
    plat: String,
    installer: String,
    curl: bool,
    git: bool,
    python: i32,
    pip: i32,
    info: String,
}

impl SystemInfoAndBuild {
    pub fn get_python(&self) -> String {
        format!("python{}", self.python)
    }

    pub fn check_install(&mut self) -> String {
        if !self.git {
            info!("install : {}", "git ");
            _install(&self.installer, "git");
        }
        if !self.curl {
            info!("install : {}", "curl ");
            _install(&self.installer, "curl");
        }
        if self.python == 0 {
            info!("install : {}", "python3 ");
            self.install("python3");

            info!("install : {}", "python3-pip ");
            self.install("python3-pip");

            self.python = base_check().python;
        }

        if self.pip == 0 {
            let res: String;
            if self.python == 2 {
                res = self.install("python-pip");
            } else if self.python == 3 {
                res = self.install("python3-pip");
            } else {
                res = "check install python or install by manul".to_string();
            }

            info!("install : python{}-pip\n{}", self.python, &res);
        }
        let gits = get_sub_map("gits");
        let _ = gits
            .iter()
            .map(|(k, v)| {
                info!("git install {}", k);
                self.git(k, v)
            })
            .collect::<Vec<_>>();

        let pys = get_sub_map("pys");
        let _ = pys
            .iter()
            .map(|(k, v)| {
                info!("py install {}", k);
                _pip_install(v);
            })
            .collect::<Vec<_>>();

        let apt = get_sub_map("apt");
        let _ = apt
            .iter()
            .map(|(k, v)| {
                info!("base install {}", k);
                self.install(v);
            })
            .collect::<Vec<_>>();

        // let _ = GIT_PLUGINS.split("[SEP]").map(|git_addr|{
        //     info!("git plugin {}:\n{}",git_addr, self.git(git_addr));
        // }).collect::<Vec<()>>();
        base_check().info
    }

    pub fn git(&self, name: &str, git_addr: &str) -> String {
        let dir_root = home(false);
        if !Path::new(&dir_root).join(&name).exists() {
            // _run_cmd(&format!("git clone {} ~/{}/{}",git_addr, &dir_root, &name))
            _run_cmd(&format!("git clone {}", git_addr))
        } else {
            format!("{} [installed]", name)
        }
    }

    pub fn install(&self, name: &str) -> String {
        _install(&self.installer, name)
    }
}

#[cfg(not(target_os = "windows"))]
pub fn base_check() -> SystemInfoAndBuild {
    let home_dir: String = home(false);
    if !Path::new(&home_dir).exists() {
        fs::create_dir(&home_dir).expect("can not create cache");
        info!("create home dir {} [{}]", &home_dir.blue(), "ok".green());
    }

    let mut sys_info = SystemInfoAndBuild {
        plat: "linux".to_string(),
        installer: "apt-get".to_string(),
        info: "".to_string(),
        git: false,
        python: 0,
        curl: false,
        pip: 0,
    };
    let plugins_str: Vec<String> = vec![
        "pip".to_string(),
        "pip3".to_string(),
        "apt-get".to_string(),
        "python".to_string(),
        "python3".to_string(),
        "yum".to_string(),
        "git".to_string(),
        "curl".to_string(),
    ]
    .iter()
    .map(|plg| {
        let mut test_result = String::from(plg);
        let mut found: bool = false;
        for path_str in vec![
            "/bin/",
            "/usr/bin/",
            "/usr/local/bin/",
            "/sbin/",
            "/usr/sbin/",
        ] {
            if Path::new(path_str).join(plg).exists() {
                if test_result.contains("pip") {
                    sys_info.pip = 2
                } else if test_result.contains("pip3") {
                    sys_info.pip = 3
                } else if test_result.contains("curl") {
                    sys_info.curl = true
                } else if test_result.contains("git") {
                    sys_info.git = true
                } else if test_result.contains("python3") {
                    sys_info.python = 3
                } else if test_result.contains("python") {
                    sys_info.python = 2
                } else if test_result.contains("yum") {
                    sys_info.installer = "yum".to_string();
                } else if test_result.contains("apt-get") {
                    sys_info.installer = "apt-get".to_string();
                }

                test_result.push_str("[ok]");
                found = true;
                break;
            }
        }
        if !found {
            test_result.push_str("[no]");
            info!("test Plug {} [{}]", plg.blue(), "no".yellow());
        } else {
            info!("test Plug {} [{}]", plg.blue(), "ok".green());
        }
        test_result.to_string()
    })
    .collect();
    sys_info.info = plugins_str.join("\n").to_string();
    sys_info
}

pub fn _run_cmd(cmd: &str) -> String {
    let mut buf = String::new();
    let output = if cfg!(target_os = "windows") {
        Command::new("cmd")
            .arg("/C")
            .arg(format!("{}", cmd))
            .output()
            .expect("failed to execute process")
    } else {
        Command::new("sh")
            .arg("-c")
            .arg(format!("{}", cmd))
            .output()
            .expect("failed to execute process")
    };

    if !output.status.success() {
        buf.push_str(match str::from_utf8(&output.stderr) {
            Ok(e) => e,
            _ => "include un utf8 charset",
        });
    }
    buf.push_str(match str::from_utf8(&output.stdout) {
        Ok(e) => e,
        _ => "include un utf8 charset",
    });
    buf
}

fn _install(installer: &str, p: &str) -> String {
    let cmd = format!("{} install -y {}", installer, p);
    _run_cmd(&cmd)
}

fn _pip_install(p: &str) -> String {
    let cmd = format!("pip install  {} -U -i https://pypi.douban.com/simple", p);
    _run_cmd(&cmd)
}

pub fn info_protocol(para: &Parameter, _: &SystemInfoAndBuild) -> (i32, String) {
    let code = 0;
    let (op, data, _) = para.get_args_str();

    let msg = match op {
        "h" => r#"
               show all information. from sys;
        // inst:       install some plugin, 
        //                git like  "git+http://github.com/xx/xx.git"
        //                py  like  "py+shadowsocks"
        //                apt/yum like  "hydra"
        check:      check install all .
            "#
        .to_string(),
        _ => {
            let s = base_check();
            let v: Value = json!({
                "plat": s.plat,
                "python": s.python,
                "pip":s.pip,
                "installer": s.installer,
                "curl": s.curl,
                "git": s.git,
                "you send msg": data,
            });
            v.to_string()
        }
    };

    (code, msg)
}

pub fn init_build() -> SystemInfoAndBuild {
    let mut s = base_check();
    info!("base check [{}]", "ok".green());
    s.check_install();
    info!("to install ... [{}]", "ok".green());
    base_check()
}
